package org.dromara.netbar.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.dromara.common.core.constant.NetbarConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.enums.FormatsType;
import org.dromara.common.core.utils.*;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.netbar.domain.NetbarClient;
import org.dromara.netbar.domain.bo.NetbarMemberBalanceLogBo;
import org.dromara.netbar.domain.vo.*;
import org.dromara.netbar.mapper.NetbarClientMapper;
import org.dromara.netbar.mapper.NetbarSeatTypeMapper;
import org.dromara.netbar.service.*;
import org.dromara.netbar.util.BkdUtils;
import org.dromara.netbar.util.CouponUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.dromara.netbar.domain.bo.NetbarMemberBo;
import org.dromara.netbar.domain.NetbarMember;
import org.dromara.netbar.mapper.NetbarMemberMapper;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 会员信息管理Service业务层处理
 *
 * @author Lion Li
 * @date 2025-06-29
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class NetbarMemberServiceImpl implements INetbarMemberService {
    private final NetbarMemberMapper baseMapper;
    private final NetbarClientMapper netbarClientMapper;
    private final NetbarSeatTypeMapper netbarSeatTypeMapper;
    private final INetbarMemberLevelService netbarMemberLevelService;
    private final INetbarCouponMemberService netbarCouponMemberService;
    private final INetbarSeatTypeService netbarSeatTypeService;
    private final INetbarGiftPackService netbarGiftPackService;
    private final INetbarAutoGrantService netbarAutoGrantService;
    private final INetbarMemberBalanceLogService netbarMemberBalanceLogService;
    /**
     * 查询会员信息管理
     *
     * @param memberId 主键
     * @return 会员信息管理
     */
    @Override
    public NetbarMemberVo queryById(Long memberId) {
        long currentTimeMillis = System.currentTimeMillis();
        NetbarMemberVo netbarMemberVo = baseMapper.selectVoById(memberId);
        String nowDateStr = DateUtils.parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, new Date());
        if (null == netbarMemberVo || null == netbarMemberVo.getMemberId()) {
            return null;
        }
        List<Long> memberIdList = Collections.singletonList(memberId);
        // 获取会员等级数据
        List<NetbarMemberLevelVo> netbarMemberLevelVos = netbarMemberLevelService.listMemberLevelByIds(Collections.singletonList(netbarMemberVo.getMemberLevelId()));
        // 在线的会员信息
        Map<Long, Object> memberMaps = RedisUtils.getCacheMapValue(RedisKeyGeneratorUtils.getOnlineMemberKey(netbarMemberVo.getTenantId()), memberId + "");
        // 会员使用的卡券
        // 查询会员使用中的卡券
        Map<String, NetbarCouponVo> netbarCouponVoMap = netbarCouponMemberService.listCouponMemberUsed(netbarMemberVo.getTenantId(), memberIdList);
        // 查询用户的卡券信息
        List<NetbarCouponMemberVo> netbarCouponMemberVos = netbarCouponMemberService.listCouponMemberGroupByMemberIds(memberIdList, nowDateStr);

        for (NetbarCouponMemberVo cmRecord : netbarCouponMemberVos) {
            if(null != cmRecord.getCoupon() && CouponUtils.checkCouponBsValid(cmRecord.getCoupon(), currentTimeMillis)){
                cmRecord.setAvailable(true);
            }
        }

        //========== 给用户增加额外信息
        // 自动使用优惠券
        netbarMemberVo.setAutoCouponMemberList(netbarCouponMemberVos.stream().filter(c -> c.getCoupon().getIsAutoUse() == 1 && c.getMemberId().equals(memberId)).toList());
        // 手动使用优惠券
        netbarMemberVo.setManualCouponMemberList(netbarCouponMemberVos.stream().filter(c -> c.getCoupon().getIsAutoUse() == 2 && c.getMemberId().equals(memberId)).toList());
        // 会员等级名称
        netbarMemberVo.setMemberLevelName(netbarMemberLevelVos.stream().filter(level -> level.getLevelId().equals(netbarMemberVo.getMemberLevelId())).findFirst().map(NetbarMemberLevelVo::getLevelName)
            .orElse(NetbarConstants.Member.DEFAULT_MEMBER_LEVEL_NAME));
        if (memberMaps != null) {
            netbarMemberVo.setClientName((String) memberMaps.get("clientNo"));
            // 计算成小时/分钟
            netbarMemberVo.setOnlineTimeStr(DateUtils.calculateOnlineTime((Long) memberMaps.get("startTime"), currentTimeMillis));
        }
        // 使用中的卡券
        List<NetbarCouponVo> couponVos = new ArrayList<>();
        for (Map.Entry<String, NetbarCouponVo> entry : netbarCouponVoMap.entrySet()) {
            if (entry.getKey().contains(memberId + "")) {
                couponVos.add(entry.getValue());
            }
            if(couponVos.size() >= 2) break;
        }
        netbarMemberVo.setInUseCouponMemberList(couponVos);
        List<NetbarMemberBalanceVo> memberBalanceVoList = baseMapper.selectMemberBalance(Collections.singletonList(memberId));
        if (!memberBalanceVoList.isEmpty()) {
            netbarMemberVo.setBaseBalance(memberBalanceVoList.get(0).getBasicBalanceSum());
            netbarMemberVo.setAwardBalance(memberBalanceVoList.get(0).getRewardBalanceSum());
        }else{
            netbarMemberVo.setBaseBalance(0L);
            netbarMemberVo.setAwardBalance(0L);
        }
        return netbarMemberVo;
    }


    @Override
    public TableDataInfo<NetbarMemberVo> queryOnlinePageList(NetbarMemberBo bo, PageQuery pageQuery) {
        long currentTimeMillis = System.currentTimeMillis();
        LambdaQueryWrapper<NetbarMember> lqw = buildQueryWrapper(bo);
        Page<NetbarMemberVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        if (CollectionUtil.isNotEmpty(result.getRecords())) {
            // 拿到会员数据
            List<NetbarMemberVo> records = result.getRecords();
            // 获取第一个会员，拿到租户ID，拼接成 redisKey
            String tenantId = records.get(0).getTenantId();
            // 获取会员等级IDs
            List<Long> memberLevelIdList = records.stream().map(NetbarMemberVo::getMemberLevelId).toList();
            // 获取会员等级数据
            List<NetbarMemberLevelVo> netbarMemberLevelVos = netbarMemberLevelService.listMemberLevelByIds(memberLevelIdList);
            Map<String, Object> memberRedisMap = null;
            // 会员ID列表
            List<Long> memberIdList = records.stream().map(NetbarMemberVo::getMemberId).toList();
            List<NetbarMemberBalanceVo> memberBalanceVoList = baseMapper.selectMemberBalance(memberIdList);
            // 在线的会员信息
            Map<Long, Map<String, Object>> memberMaps = RedisUtils.getMultiCacheMapValue(RedisKeyGeneratorUtils.getOnlineMemberKey(tenantId), new HashSet<>(memberIdList));
            // 会员使用的卡券
            String nowDateStr = DateUtils.parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, new Date());
            // 查询会员使用中的卡券
            Map<String, NetbarCouponVo> netbarCouponVoMap = netbarCouponMemberService.listCouponMemberUsed(tenantId, memberIdList);
            // 查询用户的卡券信息
            List<NetbarCouponMemberVo> netbarCouponMemberVos = netbarCouponMemberService.listCouponMemberGroupByMemberIds(records.stream().map(NetbarMemberVo::getMemberId).toList(), nowDateStr);

            for (NetbarCouponMemberVo cmRecord : netbarCouponMemberVos) {
                if(null != cmRecord.getCoupon() && CouponUtils.checkCouponBsValid(cmRecord.getCoupon(), currentTimeMillis)){
                    cmRecord.setAvailable(true);
                }
            }

            Optional<NetbarMemberBalanceVo> memberBalanceVoOptional;
            for (NetbarMemberVo item : records) {
                //========== 给用户增加额外信息
                memberBalanceVoOptional = memberBalanceVoList.stream().filter(r -> r.getMemberId().equals(item.getMemberId())).findFirst();
                if (memberBalanceVoOptional.isPresent()) {
                    item.setBaseBalance(memberBalanceVoOptional.get().getBasicBalanceSum());
                    item.setAwardBalance(memberBalanceVoOptional.get().getRewardBalanceSum());
                }
                // 自动使用优惠券
                item.setAutoCouponMemberList(netbarCouponMemberVos.stream().filter(c -> c.getCoupon().getIsAutoUse() == 1 && c.getMemberId().equals(item.getMemberId())).toList());
                // 手动使用优惠券
                item.setManualCouponMemberList(netbarCouponMemberVos.stream().filter(c -> c.getCoupon().getIsAutoUse() == 2 && c.getMemberId().equals(item.getMemberId())).toList());
                // 会员等级名称
                item.setMemberLevelName(netbarMemberLevelVos.stream().filter(level -> level.getLevelId().equals(item.getMemberLevelId())).findFirst().map(NetbarMemberLevelVo::getLevelName).orElseGet(() -> NetbarConstants.Member.DEFAULT_MEMBER_LEVEL_NAME));
                memberRedisMap = memberMaps.get(item.getMemberId());
                if (memberRedisMap != null) {
                    item.setClientName((String) memberRedisMap.get("clientNo"));
                    // 计算成小时/分钟
                    item.setOnlineTimeStr(DateUtils.calculateOnlineTime((Long) memberRedisMap.get("startTime"), currentTimeMillis));
                }
                // 使用中的卡券
                // 优化后
                String memberId = String.valueOf(item.getMemberId());
                List<NetbarCouponVo> couponVos = new ArrayList<>();
                for (Map.Entry<String, NetbarCouponVo> entry : netbarCouponVoMap.entrySet()) {
                    if (entry.getKey().contains(memberId)) {
                        couponVos.add(entry.getValue());
                    }
                    if(couponVos.size() >= 2) break;
                }

                item.setInUseCouponMemberList(couponVos);
            }
        }
        return TableDataInfo.build(result);
    }


    /**
     * 分页查询会员信息管理列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 会员信息管理分页列表
     */
    @Override
    public TableDataInfo<NetbarMemberVo> queryPageList(NetbarMemberBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<NetbarMember> lqw = buildQueryWrapper(bo);
        Page<NetbarMemberVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的会员信息管理列表
     *
     * @param bo 查询条件
     * @return 会员信息管理列表
     */
    @Override
    public List<NetbarMemberVo> queryList(NetbarMemberBo bo) {
        LambdaQueryWrapper<NetbarMember> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<NetbarMember> buildQueryWrapper(NetbarMemberBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<NetbarMember> lqw = Wrappers.lambdaQuery();
        lqw.orderByDesc(NetbarMember::getStatus, NetbarMember::getCreateTime);
        lqw.like(StringUtils.isNotBlank(bo.getMemberName()), NetbarMember::getMemberName, bo.getMemberName());
        lqw.eq(StringUtils.isNotBlank(bo.getMemberType()), NetbarMember::getMemberType, bo.getMemberType());
        lqw.eq(StringUtils.isNotBlank(bo.getAccount()), NetbarMember::getAccount, bo.getAccount());
        lqw.eq(StringUtils.isNotBlank(bo.getPhonenumber()), NetbarMember::getPhonenumber, bo.getPhonenumber());
        lqw.eq(StringUtils.isNotBlank(bo.getSex()), NetbarMember::getSex, bo.getSex());
        lqw.eq(bo.getAvatar() != null, NetbarMember::getAvatar, bo.getAvatar());
        lqw.eq(StringUtils.isNotBlank(bo.getPassword()), NetbarMember::getPassword, bo.getPassword());
        lqw.eq(bo.getBaseBalance() != null, NetbarMember::getBaseBalance, bo.getBaseBalance());
        lqw.eq(bo.getAwardBalance() != null, NetbarMember::getAwardBalance, bo.getAwardBalance());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), NetbarMember::getStatus, bo.getStatus());
        lqw.eq(StringUtils.isNotBlank(bo.getLastLoginIp()), NetbarMember::getLastLoginIp, bo.getLastLoginIp());
        lqw.eq(bo.getLastLoginTime() != null, NetbarMember::getLastLoginTime, bo.getLastLoginTime());
        lqw.eq(StringUtils.isNotBlank(bo.getAddress()), NetbarMember::getAddress, bo.getAddress());
        lqw.eq(StringUtils.isNotBlank(bo.getOpenid()), NetbarMember::getOpenid, bo.getOpenid());
        return lqw;
    }

    /**
     * 新增会员信息管理
     *
     * @param bo 会员信息管理
     * @return 是否新增成功
     */
    @Override
    public Boolean insertByBo(NetbarMemberBo bo) {
        NetbarMember add = MapstructUtils.convert(bo, NetbarMember.class);
        validEntityBeforeSave(add);
        return baseMapper.insert(add) > 0;
    }

    /**
     * 修改会员信息管理
     *
     * @param bo 会员信息管理
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(NetbarMemberBo bo) {
        NetbarMember update = MapstructUtils.convert(bo, NetbarMember.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(NetbarMember entity) {
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 校验并批量删除会员信息管理信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
    }

    @Override
    public NetbarMemberVo getMember(String tenantId, String account) {
        return baseMapper.selectVoOne(new LambdaQueryWrapper<NetbarMember>()
            .eq(NetbarMember::getTenantId, tenantId).eq(NetbarMember::getAccount, account));
    }

    @Override
    public Boolean changeStatus(NetbarMemberBo bo) {
        AssertUtils.notNull(bo.getMemberId(), "会员ID不能为空");
        NetbarMember update = new NetbarMember();
        NetbarMemberVo netbarMemberVo = baseMapper.selectVoById(bo.getMemberId());
        // AssertUtils.notTrue(bo.getStatus().equals(netbarMemberVo.getStatus()), "当前不可操作");
        update.setMemberId(bo.getMemberId());
        update.setStatus(bo.getStatus());

        LambdaUpdateWrapper<NetbarMember> lambdaUpdateWrapper = new LambdaUpdateWrapper<NetbarMember>()
            .eq(NetbarMember::getMemberId, bo.getMemberId())
            .set(NetbarMember::getStatus, bo.getStatus());
        if ("1".equals(bo.getStatus())) {
            // 登录电脑，记录上机时间
            lambdaUpdateWrapper.set(NetbarMember::getLastLoginTime, new Date());
        }
        int update1 = baseMapper.update(lambdaUpdateWrapper);
        return update1 > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean offline(NetbarMemberBo bo) {
        AssertUtils.notNull(bo, "参数不能为空");
        AssertUtils.notNull(bo.getMemberId(), "会员ID不能为空");
        NetbarMemberVo netbarMemberVo = baseMapper.selectVoById(bo.getMemberId());
        AssertUtils.notNull(netbarMemberVo, "会员信息不存在");
        String memberRedisKey = RedisKeyGeneratorUtils.getOnlineMemberKey(netbarMemberVo.getTenantId());
        // 2、获取Redis数据
        Map<String, Object> data = RedisUtils.getCacheMapValue(memberRedisKey, bo.getMemberId().toString());
        if ((null != data  && !data.isEmpty()) || "0".equals(netbarMemberVo.getStatus()) || "1".equals(netbarMemberVo.getStatus()) || "2".equals(netbarMemberVo.getStatus())) {
            // 如果状态是在线/挂机，则关闭电脑，关闭账号 1在线，2挂机
            if(null != data){
                Long clientId = (Long) data.get("clientId");
                //TODO 关闭客户端连接
                // 4、清除redis
                asyncCleanRedisData(netbarMemberVo, bo.getMemberId(), clientId);
                // 1、移除用户状态
                bo.setStatus("0");
                changeStatus(bo);

                // 3、修改座位状态
                netbarClientMapper.update(new LambdaUpdateWrapper<NetbarClient>().eq(NetbarClient::getClientId, clientId).set(NetbarClient::getStatus, "0"));

                // 4、调用Socket下机
                String macAddress = (String) data.get("macAddress");
                if(StringUtils.isNotBlank(macAddress)){
                    BkdUtils.lockscreen(macAddress);
                }
            }
            return true;
        } else if ("3".equals(netbarMemberVo.getStatus())) {
            // 如果状态是待上机，则关闭账号
            bo.setStatus("0");
            return changeStatus(bo);
        }
        log.info("已从Redis在线列表移除会员 [id={}]", bo.getMemberId());
        return false;
    }

    @Async
    @Override
    public void asyncCleanRedisData(NetbarMemberVo memberVo, Long memberId, Long clientId) {
        String tenantId = memberVo.getTenantId();
        String memberIdStr = memberId.toString();
        String clientIdStr = clientId.toString();
        try {
            String memberRedisKey = RedisKeyGeneratorUtils.getOnlineMemberKey(tenantId);
            String clientRedisKey = RedisKeyGeneratorUtils.getOnlineClientKey(tenantId);
            String couponKey = RedisKeyGeneratorUtils.getMemberUseCouponKey(tenantId);
            // 重试机制删除
            retryDelete(memberRedisKey, memberIdStr, "用户Redis数据");
            retryDelete(clientRedisKey, clientIdStr, "客户端Redis数据");
            retryDelete(couponKey, RedisKeyGeneratorUtils.getMemberUseCouponHKey(memberId, NetbarConstants.Coupon.TYPE_3), NetbarConstants.Coupon.TYPE_3+"Redis数据");
            retryDelete(couponKey, RedisKeyGeneratorUtils.getMemberUseCouponHKey(memberId, NetbarConstants.Coupon.TYPE_2), NetbarConstants.Coupon.TYPE_2+"Redis数据");
            retryDelete(couponKey, RedisKeyGeneratorUtils.getMemberUseCouponHKey(memberId, NetbarConstants.Coupon.TYPE_4), "折扣券Redis数据");

            log.info("异步清理Redis数据完成: memberId={}", memberId);
        } catch (Exception e) {
            log.error("异步清理Redis数据失败: memberId={}", memberId, e);
            // 可以加入失败重试队列
//            handleRedisCleanFailure(memberVo, memberId, clientId);
        }
    }

    private void retryDelete(String key, String field, String desc) {
        int maxRetries = 20;
        for (int i = 0; i < maxRetries; i++) {
            try {
                Object o = RedisUtils.delCacheMapValue(key, field);
                if (null != o) {
                    System.out.println("qvq");
                    log.debug("删除{}成功: key={}, field={}", desc, key, field);
                    return;
                }
            } catch (Exception e) {
                log.warn("删除{}失败，第{}次重试: {}", desc, i+1, e.getMessage());
                try {
                    Thread.sleep(310 * (i + 1)); // 递增等待
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
        }
        log.warn("删除{}最终失败: key={}, field={}", desc, key, field);
    }

// 启动类要加上  注解


    /**
     * 扣除余额并记录日志
     *
     * @param memberId       会员ID
     * @param consumedAmount 需要扣除的余额（分）
     * @return 操作是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public NetbarMemberVo deductBalance(Long memberId, long consumedAmount) {
        // 获取用户信息并加锁
        NetbarMemberVo member = queryById(memberId);
        AssertUtils.notNull(member, "会员不存在");
        long awardBalance = member.getAwardBalance(); // 300
        long baseBalance = member.getBaseBalance(); // 200
        // 优先扣除奖励余额
        long remainingAmount = consumedAmount; // 500
        if (awardBalance > 0) {
            long deductFromAward = Math.min(awardBalance, remainingAmount); // 奖励余额 跟 扣除金额 取最小值
            awardBalance -= deductFromAward; // 奖励余额扣除最小值
            remainingAmount -= deductFromAward; // 总需要扣除的金额减去已扣除的奖励金额
            // 写入余额变动表日志
            this.subtractBalance(memberId, deductFromAward, 2L); // 减少奖励余额
        }

        // 剩余部分扣除基础余额
        if (remainingAmount > 0) {
            baseBalance -= remainingAmount;
            // 写入余额变动表日志
            this.subtractBalance(memberId, remainingAmount, 1L); // 减少基础余额
        }

        // 更新余额
//        boolean success = baseMapper.update(new LambdaUpdateWrapper<NetbarMember>()
//            .eq(NetbarMember::getMemberId, memberId)
//            .set(NetbarMember::getBaseBalance, baseBalance)
//            .set(NetbarMember::getAwardBalance, awardBalance)) > 0;
        member.setBaseBalance(baseBalance);
        member.setAwardBalance(awardBalance);
        //TODO 记录余额变动日志
//        if (success) {
//            NetbarBalanceLog log = new NetbarBalanceLog();
//            log.setMemberId(memberId);
//            log.setChangeAmount(-consumedAmount);
//            log.setBalanceType(remainingAmount > 0 ? 1 : 2); // 1:基础余额 2:奖励余额
//            log.setCurrentBalance(baseBalance + awardBalance);
//            log.setRemark("消费扣款");
//            log.setCreateTime(LocalDateTime.now());
//            balanceLogMapper.insert(log);
//        }
        return member;
    }

    /**
     * 处理余额不足的会员（下机流程）
     * 这是一个独立的事务方法，保证数据库扣费和Redis清理的原子性
     *
     * @param tenantId       租户ID
     * @param memberId       会员ID
     * @param clientId       客户端ID
     * @param consumedAmount 已消费金额
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleOffline(String tenantId, Long memberId, Long clientId, long consumedAmount) {
        // 1、对数据库中该用户的余额进行扣除 (先扣费，确保消费记录不会丢失)
        // 假设 deductBalance 方法会更新用户余额
        NetbarMemberVo netbarMemberVo = this.deductBalance(memberId, consumedAmount);
        // TODO 接入到管理端通知，提示问题原因
        if (null == netbarMemberVo) {
            // 如果扣费失败，则抛出异常，事务回滚，不执行后续操作
            throw new RuntimeException("数据库扣费失败，会员ID: " + memberId);
        }

        // 2、调取下机操作，余额不充足的会员，使用原子删除操作redis
        // HDEL 是原子操作
        this.offline(new NetbarMemberBo() {{
            setMemberId(memberId);
        }});

        // 3、调取下机操作
        // 这里可以是调用另一个服务、发送消息队列、或直接执行下机逻辑
        // 比如：
        // netbarMachineService.forceLogoff(clientId, "余额不足，系统自动下机");
        log.info("已触发客户端 [id={}] 的下机操作", clientId);

        // 可以在这里记录一条详细的消费日志
        // billingLogService.createLog(memberId, consumedAmount, "余额不足自动下机");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> checkOnlineMembers(NetbarMemberBo bo) {
        return null;
    }

    /**
     * 添加奖励余额
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addMemberAwardBalance(Long memberId, Long couponAmount) {
        // 1、参数校验
        AssertUtils.notNull(memberId, "会员ID不能为空");
        AssertUtils.notNull(couponAmount, "充值金额不能为空");
        AssertUtils.notTrue(couponAmount <= 0, "充值金额不能小于0");
        // 2、充值
        addBalance(memberId, couponAmount, 2L);
        // 3、获取会员信息
        NetbarMemberVo netbarMemberVo = queryById(memberId);
        AssertUtils.notNull(netbarMemberVo, "会员信息不存在");
        // 4、判断是否在线，如果在线 修改一下redis中的余额
        String memberRedisKey = netbarMemberVo.getTenantId() + NetbarConstants.ONLINE_MEMBER_KEY;
        Map<String, Object> onlineMemberMap = RedisUtils.getCacheMapValue(memberRedisKey, memberId.toString());
        if (null != onlineMemberMap && !onlineMemberMap.keySet().isEmpty()) {
            onlineMemberMap.put("baseBalance", netbarMemberVo.getBaseBalance());
            onlineMemberMap.put("awardBalance", netbarMemberVo.getAwardBalance());
            RedisUtils.setCacheMapValue(memberRedisKey, memberId.toString(), onlineMemberMap);
        }
        // 5、TODO 写入日志记录
    }


    /**
     * 从会员余额日志中获取会员余额总量
     * @param memberId 会员id
     * @return
     */
    @Override
    public NetbarMemberBalanceVo getMemberBalance(Long memberId){
        List<NetbarMemberBalanceVo> list = baseMapper.selectMemberBalance(Collections.singletonList(memberId));
        if (list.isEmpty()) {
            NetbarMemberBalanceVo netbarMemberBalanceVo = new NetbarMemberBalanceVo();
            netbarMemberBalanceVo.setBasicBalanceSum(0L);
            netbarMemberBalanceVo.setRewardBalanceSum(0L);
            netbarMemberBalanceVo.setMemberId(memberId);
            return netbarMemberBalanceVo;
        }
        return list.get(0);
    }

    @Override
    public NetbarMemberMapper netbarMemberMapper() {
        return baseMapper;
    }

    /**
     * 会员等级升级
     * @param memberId 会员id
     * @param memberLevelId 会员等级id
     */
    @Override
    public void levelUpgrade(Long memberId, Long memberLevelId) {
        // 先获取用户信息
        NetbarMemberVo netbarMemberVo = baseMapper.selectVoOne(new LambdaQueryWrapper<NetbarMember>()
            .eq(NetbarMember::getMemberId, memberId)
            .select(NetbarMember::getMemberId, NetbarMember::getMemberLevelId, NetbarMember::getMemberLevelTime)
        );
        // 拿到之前的会员等级
        Long oldMemberLevelId = netbarMemberVo.getMemberLevelId();
        if(null == oldMemberLevelId || (netbarMemberLevelService.compareMemberLevelAmount(oldMemberLevelId, memberLevelId))){
            //之前没有会员等级 或者 如果要升级的会员等级 > 当前会员等级
            Date date = new Date();
            // 升级（修改数据库）
            baseMapper.update(new LambdaUpdateWrapper<NetbarMember>()
                .eq(NetbarMember::getMemberId, memberId)
                .set(NetbarMember::getMemberLevelId, memberLevelId)
                .set(NetbarMember::getMemberLevelTime, date)
            );
            // 获取Redis种是否有该会员的在线信息
            String memberRedisKey = netbarMemberVo.getTenantId() + NetbarConstants.ONLINE_MEMBER_KEY;
            Map<String, Object> onlineMemberMap = RedisUtils.getCacheMapValue(memberRedisKey, memberId.toString());
            if (null != onlineMemberMap && !onlineMemberMap.keySet().isEmpty()) {
                Long seatTypeId = (Long) onlineMemberMap.get("seatTypeId");
                Long memberPrice = netbarSeatTypeService.getSeatPriceByMemberLevelAndSeatType(memberLevelId, seatTypeId);
                onlineMemberMap.put("basePrice", memberPrice);
                onlineMemberMap.put("memberLevelName", netbarMemberLevelService.getMemberLevelNameByLevelId(memberLevelId));
                RedisUtils.setCacheMapValue(memberRedisKey, memberId.toString(), onlineMemberMap);
            }
        }
    }

    @Override
    public void updateMemberPassword(Long memberId, String newPassword) {
        AssertUtils.notNull(memberId, "会员ID不能为空");
        AssertUtils.notTrue(newPassword.length() >= 24, "密码长度不能超过24位");
        baseMapper.update(new LambdaUpdateWrapper<NetbarMember>()
            .eq(NetbarMember::getMemberId, memberId)
            .set(NetbarMember::getPassword, newPassword)
        );
    }

    @Override
    public String getMemberLevelName(Long memberId) {
        NetbarMemberVo netbarMemberVo = baseMapper.selectVoOne(new LambdaQueryWrapper<NetbarMember>().select(NetbarMember::getMemberLevelId).eq(NetbarMember::getMemberId, memberId));
        if (null == netbarMemberVo) {
            return null;
        }
        return netbarMemberLevelService.getMemberLevelNameByLevelId(netbarMemberVo.getMemberLevelId());
    }


    /**
     * 添加余额
     * @param memberId 会员id
     * @param couponAmount 变动金额
     * @param balanceType 1:基本余额 2: 优惠券余额(奖励余额)
     */
    @Override
    public void addBalance(Long memberId, Long couponAmount, Long balanceType) {
        // 先获取用户余额
        NetbarMemberBalanceVo memberBalanceVo = getMemberBalance(memberId);
        // 添加余额变动记录
        NetbarMemberBalanceLogBo balanceLogVo = new NetbarMemberBalanceLogBo();
        balanceLogVo.setMemberId(memberId);
        balanceLogVo.setAmount(couponAmount);
        balanceLogVo.setBalanceType(balanceType);
        if (2 == balanceType) {
            // 奖励余额
            balanceLogVo.setBalanceBefore(memberBalanceVo.getRewardBalanceSum());
            balanceLogVo.setBalanceAfter(memberBalanceVo.getRewardBalanceSum() + couponAmount);
        }else{
            balanceLogVo.setBalanceBefore(memberBalanceVo.getBasicBalanceSum());
            balanceLogVo.setBalanceAfter(memberBalanceVo.getBasicBalanceSum() + couponAmount);
        }
        balanceLogVo.setBusinessType(1L); // 1充值
        netbarMemberBalanceLogService.insertByBo(balanceLogVo);
    }

    /**
     * 减会员余额
     */
    @Override
    public void subtractBalance(Long memberId, Long couponAmount, Long balanceType) {
        // 先获取用户余额
        NetbarMemberBalanceVo memberBalanceVo = getMemberBalance(memberId);
        // 添加余额变动记录
        NetbarMemberBalanceLogBo balanceLogVo = new NetbarMemberBalanceLogBo();
        balanceLogVo.setMemberId(memberId);
        balanceLogVo.setAmount(-couponAmount);
        balanceLogVo.setBalanceType(balanceType);
        if (2 == balanceType) {
            // 奖励余额
            balanceLogVo.setBalanceBefore(memberBalanceVo.getRewardBalanceSum());
            balanceLogVo.setBalanceAfter(memberBalanceVo.getRewardBalanceSum() - couponAmount);
        }else{
            balanceLogVo.setBalanceBefore(memberBalanceVo.getBasicBalanceSum());
            balanceLogVo.setBalanceAfter(memberBalanceVo.getBasicBalanceSum() - couponAmount);
        }
        balanceLogVo.setBusinessType(2L); // 1充值
        netbarMemberBalanceLogService.insertByBo(balanceLogVo);
    }

    @Override
    public Map<Long, Integer> countMapByMemberLevelIds(Long[] levelIds) {
        return baseMapper.queryCountMapByMemberLevelIds(levelIds);
    }
    // TODO 控制电脑锁屏

    // TODO 控制电脑关机

    // TODO 控制电脑会员下线
}
